/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * ClusterGenerator.java
 * Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.datagenerators;

import weka.core.Option;
import weka.core.Range;
import weka.core.Utils;
import weka.datagenerators.DataGenerator;

import java.util.Enumeration;
import java.util.Vector;

/**
 * Abstract class for cluster data generators.
 * <p/>
 * 
 * Example usage as the main of a datagenerator called RandomGenerator:
 * 
 * <pre>
 * public static void main(String[] args) {
 * 	try {
 * 		DataGenerator.makeData(new RandomGenerator(), args);
 * 	} catch (Exception e) {
 * 		e.printStackTrace();
 * 		System.err.println(e.getMessage());
 * 	}
 * }
 * </pre>
 * <p/>
 * 
 * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 1.6 $
 */
public abstract class ClusterGenerator extends DataGenerator {

	/** for serialization */
	private static final long serialVersionUID = 6131722618472046365L;

	/** Number of attribute the dataset should have */
	protected int m_NumAttributes;

	/** class flag */
	protected boolean m_ClassFlag = false;

	/** Stores which columns are boolean (default numeric) */
	protected Range m_booleanCols;

	/** Stores which columns are nominal (default numeric) */
	protected Range m_nominalCols;

	/**
	 * initializes the generator
	 */
	public ClusterGenerator() {
		super();

		setNumAttributes(defaultNumAttributes());
	}

	/**
	 * Returns an enumeration describing the available options.
	 * 
	 * @return an enumeration of all the available options.
	 */
	public Enumeration listOptions() {
		Vector result = enumToVector(super.listOptions());

		result.addElement(new Option("\tThe number of attributes (default "
				+ defaultNumAttributes() + ").", "a", 1, "-a <num>"));

		result.addElement(new Option(
				"\tClass Flag, if set, the cluster is listed in extra attribute.",
				"c", 0, "-c"));

		result.addElement(new Option("\tThe indices for boolean attributes.",
				"b", 1, "-b <range>"));

		result.addElement(new Option("\tThe indices for nominal attributes.",
				"m", 1, "-m <range>"));

		return result.elements();
	}

	/**
	 * Sets the options.
	 * 
	 * @param options
	 *            the options
	 * @throws Exception
	 *             if invalid option
	 */
	public void setOptions(String[] options) throws Exception {
		String tmpStr;

		super.setOptions(options);

		tmpStr = Utils.getOption('a', options);
		if (tmpStr.length() != 0)
			setNumAttributes(Integer.parseInt(tmpStr));
		else
			setNumAttributes(defaultNumAttributes());

		setClassFlag(Utils.getFlag('c', options));

		tmpStr = Utils.getOption('b', options);
		setBooleanIndices(tmpStr);
		m_booleanCols.setUpper(getNumAttributes());

		tmpStr = Utils.getOption('m', options);
		setNominalIndices(tmpStr);
		m_nominalCols.setUpper(getNumAttributes());

		// check indices
		tmpStr = checkIndices();
		if (tmpStr.length() > 0)
			throw new IllegalArgumentException(tmpStr);
	}

	/**
	 * Gets the current settings of the classifier.
	 * 
	 * @return an array of strings suitable for passing to setOptions
	 */
	public String[] getOptions() {
		Vector result;
		String[] options;
		int i;

		result = new Vector();
		options = super.getOptions();
		for (i = 0; i < options.length; i++)
			result.add(options[i]);

		result.add("-a");
		result.add("" + getNumAttributes());

		if (getClassFlag())
			result.add("-c");

		if (!getBooleanCols().toString().equalsIgnoreCase("empty")) {
			result.add("-b");
			result.add("" + getBooleanCols());
		}

		if (!getNominalCols().toString().equalsIgnoreCase("empty")) {
			result.add("-m");
			result.add("" + getNominalCols());
		}

		return (String[]) result.toArray(new String[result.size()]);
	}

	/**
	 * returns the default number of attributes
	 * 
	 * @return the default number of attributes
	 */
	protected int defaultNumAttributes() {
		return 10;
	}

	/**
	 * Sets the number of attributes the dataset should have.
	 * 
	 * @param numAttributes
	 *            the new number of attributes
	 */
	public void setNumAttributes(int numAttributes) {
		m_NumAttributes = numAttributes;
		getBooleanCols().setUpper(getNumAttributes());
		getNominalCols().setUpper(getNumAttributes());
	}

	/**
	 * Gets the number of attributes that should be produced.
	 * 
	 * @return the number of attributes that should be produced
	 */
	public int getNumAttributes() {
		return m_NumAttributes;
	}

	/**
	 * Returns the tip text for this property
	 * 
	 * @return tip text for this property suitable for displaying in the
	 *         explorer/experimenter gui
	 */
	public String numAttributesTipText() {
		return "The number of attributes the generated data will contain.";
	}

	/**
	 * Sets the class flag, if class flag is set, the cluster is listed as class
	 * atrribute in an extra attribute.
	 * 
	 * @param classFlag
	 *            the new class flag
	 */
	public void setClassFlag(boolean classFlag) {
		m_ClassFlag = classFlag;
	}

	/**
	 * Gets the class flag.
	 * 
	 * @return the class flag
	 */
	public boolean getClassFlag() {
		return m_ClassFlag;
	}

	/**
	 * Returns the tip text for this property
	 * 
	 * @return tip text for this property suitable for displaying in the
	 *         explorer/experimenter gui
	 */
	public String classFlagTipText() {
		return "If set to TRUE, lists the cluster as an extra attribute.";
	}

	/**
	 * Sets which attributes are boolean
	 * 
	 * @param rangeList
	 *            a string representing the list of attributes. Since the string
	 *            will typically come from a user, attributes are indexed from
	 *            1. <br/>
	 *            eg: first-3,5,6-last
	 * @throws IllegalArgumentException
	 *             if an invalid range list is supplied
	 */
	public void setBooleanIndices(String rangeList) {
		m_booleanCols.setRanges(rangeList);
	}

	/**
	 * Sets which attributes are boolean.
	 * 
	 * @param value
	 *            the range to use
	 */
	public void setBooleanCols(Range value) {
		m_booleanCols.setRanges(value.getRanges());
	}

	/**
	 * returns the range of boolean attributes.
	 * 
	 * @return the range of boolean attributes
	 */
	public Range getBooleanCols() {
		if (m_booleanCols == null)
			m_booleanCols = new Range();

		return m_booleanCols;
	}

	/**
	 * Returns the tip text for this property
	 * 
	 * @return tip text for this property suitable for displaying in the
	 *         explorer/experimenter gui
	 */
	public String booleanColsTipText() {
		return "The range of attributes that are generated as boolean ones.";
	}

	/**
	 * Sets which attributes are nominal
	 * 
	 * @param rangeList
	 *            a string representing the list of attributes. Since the string
	 *            will typically come from a user, attributes are indexed from
	 *            1. <br/>
	 *            eg: first-3,5,6-last
	 * @throws IllegalArgumentException
	 *             if an invalid range list is supplied
	 */
	public void setNominalIndices(String rangeList) {
		m_nominalCols.setRanges(rangeList);
	}

	/**
	 * Sets which attributes are nominal.
	 * 
	 * @param value
	 *            the range to use
	 */
	public void setNominalCols(Range value) {
		m_nominalCols.setRanges(value.getRanges());
	}

	/**
	 * returns the range of nominal attributes
	 * 
	 * @return the range of nominal attributes
	 */
	public Range getNominalCols() {
		if (m_nominalCols == null)
			m_nominalCols = new Range();

		return m_nominalCols;
	}

	/**
	 * Returns the tip text for this property
	 * 
	 * @return tip text for this property suitable for displaying in the
	 *         explorer/experimenter gui
	 */
	public String nominalColsTipText() {
		return "The range of attributes to generate as nominal ones.";
	}

	/**
	 * check if attribute types are not contradicting
	 * 
	 * @return empty string if no problem, otherwise error message
	 */
	protected String checkIndices() {
		for (int i = 1; i < getNumAttributes() + 1; i++) {
			m_booleanCols.isInRange(i);
			if (m_booleanCols.isInRange(i) && m_nominalCols.isInRange(i)) {
				return "Error in attribute type: Attribute " + i
						+ " is set boolean and nominal.";
			}
		}
		return "";
	}
}
